﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using System.Collections.Generic;
using System.Linq;
using Utils;
using System.Text;

namespace SlQuery
{
    public class SlQuery : IEnumerable<HtmlElement>
    {
        public static SlQuery Get() { return new SlQuery(); }
        public static SlQuery Get(HtmlElement htmlElement) { return new SlQuery(htmlElement); }
        public static SlQuery Get(string selector) { return new SlQuery(selector); }
        public static SlQuery Get(string selector, SlQuery scope) { return new SlQuery(scope, selector); }
        public static SlQuery Get(string selector, HtmlElement scope) { return new SlQuery(scope, selector); }

        public static SlQuery Create(string tagName) { return new SlQuery(HtmlPage.Document.CreateElement(tagName)); }
        
        public SlQuery()
        {
            _elements.Add(HtmlPage.Document.Body);

            TagElements();
        }
        public SlQuery(HtmlElement htmlElement)
        {
            _elements.Add(htmlElement);

            TagElements();
        }
        public SlQuery(IEnumerable<HtmlElement> htmlElements)
        {
            _elements.AddRange(htmlElements);

            TagElements();
        }
        public SlQuery(string selector)
        {
            var selectors = selector.Split(_separators, StringSplitOptions.RemoveEmptyEntries);

            if (selectors.Length > 0)
            {
                if (selectors[0][0] == _idTag)
                {
                    var element = HtmlPage.Document.GetElementById(selectors[0].Substring(1));
                    Find(element, selectors.Skip(1));
                }
                else
                {
                    Find(HtmlPage.Document.Body, selectors);
                }
            }
            TagElements();
        }
        public SlQuery(SlQuery scope, string selector)
        {
            var selectors = selector.Split(_separators, StringSplitOptions.RemoveEmptyEntries);

            if (selectors.Length > 0)
            {
                foreach (var element in scope._elements)
                {
                    Find(element, selectors);
                }
            }
            TagElements();
        }
        public SlQuery(HtmlElement scope, string selector)
        {
            var selectors = selector.Split(_separators, StringSplitOptions.RemoveEmptyEntries);

            if (selectors.Length > 0)
            {
                Find(scope, selectors);
            }
            TagElements();
        }

        public int Count { get { return _elements.Count(); } }
        public HtmlElement this[int i]
        {
            get { return _elements[i]; }
        }

        public SlQuery clone(bool cloneChildren)
        {
            if (Count < 1) return null;

            var clone = _elements.First().Invoke("cloneNode", cloneChildren) as HtmlElement;

            UntagElement(clone, true);
                       
            return new SlQuery(clone);
        }

        public SlQuery children()
        {
            var children = new List<HtmlElement>();

            foreach(var element in _elements)
            {
                foreach (var child in element.Children)
                {
                    children.Add(child as HtmlElement);
                }
            }
            return new SlQuery(children);
        }
        public SlQuery append(SlQuery child)
        {
            foreach (var element in _elements)
            {
                foreach (var childElement in child._elements)
                {
                    element.AppendChild(childElement);
                }
            }
            return this;
        }
        public SlQuery append(string html)
        {
            foreach (var element in _elements)
            {
                var innerHtml = element.GetProperty("innerHTML");
                element.SetProperty("innerHTML", string.Format("{0}{1}", innerHtml, html));
            }
            return this;
        }

        public SlQuery remove()
        {
            foreach (var element in _elements)
            {
                remove(element);
            }
            return this;
        }
        public void remove(HtmlElement htmlElement)
        {
            // NOTE: Should make shure to remove event handlers!
            htmlElement.Parent.RemoveChild(htmlElement);
        }

        public string attr(string name)
        {
            if (Count < 1) return string.Empty;

            return _elements.First().GetAttribute(name);
        }
        public SlQuery attr(string name, string value)
        {
            foreach (var element in _elements)
            {
                element.SetAttribute(name, value);
            }
            return this;
        }
        public SlQuery removeAttr(string name)
        {
            foreach (var element in _elements)
            {
                element.RemoveAttribute(name);
            }
            return this;
        }

        public string html()
        {
            if (Count < 1) return string.Empty;

            return (string)_elements.First().GetProperty("innerHTML");
        }
        public SlQuery html(string value)
        {
            foreach (var element in _elements)
            {
                element.SetProperty("innerHTML", value);
            }
            return this;
        }

        public string outerHtml()
        {
            if (Count < 1) return string.Empty;

            return (string)_elements.First().GetProperty("outerHTML");
        }

        public string text()
        {
            if (Count < 1) return string.Empty;

            if (Count == 1) return (string)_elements.First().GetProperty("innerText");

            var text = new StringBuilder();

            foreach (var element in _elements)
            {
                text.Append((string)element.GetProperty("innerText"));
            }
            return text.ToString();
        }
        public SlQuery text(string value)
        {
            foreach (var element in _elements)
            {
                element.SetProperty("innerText", value);
            }
            return this;
        }

        public string val()
        {
            if (Count < 1) return string.Empty;

            return (string)_elements.First().GetProperty("value");
        }
        public SlQuery val(string value)
        {
            foreach (var element in _elements)
            {
                element.SetProperty("value", value);
            }
            return this;
        }

        public string id()
        {
            if (Count < 1) return string.Empty;

            return (string)_elements.First().Id;
        }

        public SlQuery addClass(string name)
        {
            foreach (var element in _elements)
            {
                if (!element.CssClass.Split(' ').Contains(name))
                {
                    element.CssClass = string.Format("{0} {1}", element.CssClass, name);
                }
            }
            return this;
        }
        public bool hasClass(string name)
        {
            foreach (var element in _elements)
            {
                if (hasClass(element, name))
                {
                    return true;
                }
            }
            return false;
        }
        public SlQuery removeClass(string name)
        {
            foreach (var element in _elements)
            {
                removeClass(element, name);
            }
            return this;
        }
        public SlQuery toggleClass(string name)
        {
            foreach (var element in _elements)
            {
                if (hasClass(element, name))
                {
                    removeClass(element, name);
                }
                else
                {
                    element.CssClass = string.Format("{0} {1}", element.CssClass, name);
                }
            }
            return this;
        }

        public SlQuery bind(string eventName, EventHandler eventHandler)
        {
            foreach (var element in _elements)
            {
                var id = (int)(double)element.GetProperty("slQ");

                if (!_eventHandlers.ContainsKey(id))
                {
                    _eventHandlers.Add(id, new Dictionary<string, EventHandler>());
                }
                _eventHandlers[id].Add(eventName, eventHandler);

                element.AttachEvent(eventName, eventHandler);
            }
            return this;
        }
        public SlQuery unbind(string eventName)
        {
            foreach (var element in _elements)
            {
                var id = (int)(double)element.GetProperty("slQ");

                var eventHandlers = _eventHandlers[id];

                var eventHandler = eventHandlers[eventName];

                element.DetachEvent(eventName, eventHandler);

                eventHandlers.Remove(eventName);
            }
            return this;
        }

        public SlQuery click(EventHandler eventHandler)
        {
            bind("click", eventHandler);

            return this;
        }
        public SlQuery hover(EventHandler enter, EventHandler leave)
        {
            bind("mouseenter", enter);
            bind("mouseleave", leave);

            return this;
        }

        public override string ToString()
        {
            if (Count == 1)
            {
                return _elements.First().Id == string.Empty ? _elements.First().TagName : string.Format("{0}(\"{1}\")", _elements.First().TagName, _elements.First().Id);
            }
            else
            {
                return string.Format("{0} elements", Count);
            }
        }

        protected static bool hasClass(HtmlElement element, string name)
        {
            return element.CssClass.Split(' ').Contains(name);
        }
        protected static void removeClass(HtmlElement element, string name)
        {
            var parts = element.CssClass.Split(' ');

            var cleaned = parts.Where(part => part != string.Empty && part != name);

            element.CssClass = cleaned.ToString(" ");
        }

        protected void Find(HtmlElement element, IEnumerable<string> filters)
        {
            if (filters.Count() == 0)
            {
                _elements.Add(element);
                return;
            }
            var currentFilter = filters.First();
            var isLeaf = filters.Count() == 1;

            foreach (var child in element.Children)
            {
                var childElement = child as HtmlElement;

                if (childElement != null)
                {
                    if (Match(childElement, currentFilter))
                    {
                        if (isLeaf)
                        {
                            _elements.Add(childElement);
                        }
                        else
                        {
                            Find(childElement, filters.Skip(1));
                        }
                    }
                    else
                    {
                        Find(childElement, filters);
                    }
                }
            }
        }
        protected bool Match(HtmlElement element, string filter)
        {
            var classTag = filter.IndexOf(_classTag);

            if (classTag < 0)
            {
                return element.TagName == filter.ToLower();
            }
            if (classTag == 0)
            {
                var className = filter.Substring(1);

                return element.CssClass.Split(' ').Contains(className);
            }
            else
            {
                return Match(element, filter.Substring(0, classTag)) && Match(element, filter.Substring(classTag));
            }
        }

        protected void TagElements()
        {
            foreach (var element in _elements)
            {
                if (element.GetProperty("slQ") == null)
                {
                    element.SetProperty("slQ", ++_id);
                }
            }
        }
        protected void UntagElement(HtmlElement element, bool untagChildren)
        {
            if (element.GetProperty("slQ") != null)
            {
                element.SetProperty("slQ", null);
            }
            if (untagChildren)
            {
                foreach (var child in element.Children)
                {
                    UntagElement(child as HtmlElement, true);
                }
            }
        }

        protected readonly char[] _separators = { ' ' };
        protected const char _idTag = '#';
        protected const char _classTag = '.';

        protected List<HtmlElement> _elements = new List<HtmlElement>();

        protected static int _id;
        protected static Dictionary<int, Dictionary<string, EventHandler>> _eventHandlers = new Dictionary<int, Dictionary<string, EventHandler>>();

        public IEnumerator<HtmlElement> GetEnumerator()
        {
            return _elements.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return _elements.GetEnumerator();
        }
    }
}
